/*
 * Copyright 2011 Mark Davidson.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.dctmcommons.impl;

import java.util.ArrayList;
import java.util.List;

import org.dctmcommons.IParamCheck;
import org.dctmcommons.IPathBuilder;
import org.dctmcommons.ObjectNotFoundException;

import static org.dctmcommons.DctmCommonsConstants.*;

import com.documentum.fc.client.IDfACL;
import com.documentum.fc.client.IDfFolder;
import com.documentum.fc.client.IDfSession;
import com.documentum.fc.common.DfDocbaseConstants;
import com.documentum.fc.common.DfException;
import com.documentum.fc.common.DfLogger;

/**
 * Default implementation of {@link IPathBuilder}. Example usage:
 * 
 * <pre>
 * IDfFolder[] folders = PathBuilder.from("/mycabinet/folder1")
 *                                  .setDefaultAcl(acl1)
 *                                  .setDefaultType("dm_folder")
 *                                  .add("folder2")
 *                                  .add("folder3")
 *                                  .setDefaultType("my_folder")
 *                                  .add("folder4")
 *                                  .add("folder5", "my_folder2", acl2)
 *                                  .build();
 * </pre>
 * 
 * <p>
 * The above code would produce the folder path
 * '/mycabinet/folder1/folder2/folder3/folder4/folder5'. The generated folders
 * are detailed below:
 * </p>
 * 
 * <ul>
 * <li>folder2: type = dm_folder, acl = acl1</li>
 * <li>folder3: type = dm_folder, acl = acl1</li>
 * <li>folder4: type = my_folder, acl = acl1</li>
 * <li>folder5: type = my_folder2, acl = acl2</li>
 * </ul>
 * 
 * <p>
 * The default ACL is null. The default object type is dm_folder. These defaults
 * can be overridden at any point of the path building process as demonstrated
 * in the above code. The add() method is overridden to allow the object type
 * and ACL to be explicitly set on a per path element basis if required.
 * </p>
 * 
 * @author Mark Davidson
 */
public class PathBuilder implements IPathBuilder {

	/** The list of path elements. */
	private List<PathElement> pathElements = new ArrayList<PathElement>();
	
	/** Whether to check the existence of the added folder elements. */
	private boolean requiresTargetFolderCheck = true;
	
	/** Whether to check the existence of the base path. */
	private boolean requiresBasePathCheck = true;
	
	/** The base path. */
	private String basePath = null;
	
	/** The default object type name. */
	private String defaultType = DfDocbaseConstants.DM_FOLDER;
	
	/** The default ACL. */
	private IDfACL defaultAcl = null;
	
	/** The parameter check instance. */
	private static final IParamCheck paramCheck = ParamCheck.getInstance();
	
	/** Default constructor. */
	private PathBuilder(final String basePath) {
		paramCheck.checkString(basePath, "basePath");
		this.basePath = basePath;
	}
	
	/**
	 * Factory method.
	 * @return A path builder instance
	 */
	public static IPathBuilder from(final String basePath) {
		return new PathBuilder(basePath);
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#add(java.lang.String)
	 */
	@Override
	public IPathBuilder add(final String name) {
		addPathElement(name, null, null);
		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#add(java.lang.String, java.lang.String)
	 */
	@Override
	public IPathBuilder add(final String name, final String type) {
		addPathElement(name, type, null);
		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#add(java.lang.String, java.lang.String, com.documentum.fc.client.IDfACL)
	 */
	@Override
	public IPathBuilder add(final String name,
							final String type,
							final IDfACL acl) {
		addPathElement(name, type, acl);
		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#add(java.lang.String, com.documentum.fc.client.IDfACL)
	 */
	@Override
	public IPathBuilder add(final String name, final IDfACL acl) {
		addPathElement(name, null, acl);
		return this;
	}
	
	/**
	 * Adds a new path element.
	 * @param name The folder name
	 * @param type The object type name
	 * @param acl The ACL
	 */
	private void addPathElement(final String name,
								final String type,
								final IDfACL acl) {
		
		final PathElement pathElement = new PathElement();

		pathElement.name = name;
		pathElement.type = (type == null ? defaultType : type);
		pathElement.acl = (acl == null ? defaultAcl : acl);

		pathElements.add(pathElement);
	}

	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#setDefaultType(java.lang.String)
	 */
	@Override
	public IPathBuilder setDefaultType(final String defaultType) {
		if (defaultType == null) {
			this.defaultType = DfDocbaseConstants.DM_FOLDER;
		} else {
			this.defaultType = defaultType;
		}
		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#setDefaultAcl(com.documentum.fc.client.IDfACL)
	 */
	@Override
	public IPathBuilder setDefaultAcl(final IDfACL defaultAcl) {
		this.defaultAcl = defaultAcl;
		return this;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#setCheckTargetFolderExists(boolean)
	 */
	@Override
	public IPathBuilder setCheckTargetFolderExists(
												final boolean requiresCheck) {
		requiresTargetFolderCheck = requiresCheck;
		return this;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#setCheckBasePathExists(boolean)
	 */
	@Override
	public IPathBuilder setCheckBasePathExists(final boolean requiresCheck) {
		requiresBasePathCheck = requiresCheck;
		return this;
	}

	/*
	 * (non-Javadoc)
	 * @see org.dctmcommons.IPathBuilder#build(com.documentum.fc.client.IDfSession)
	 */
	@Override
	public IDfFolder[] build(final IDfSession session) throws DfException {
		
		paramCheck.checkObject(session, "session");
		
		final List<IDfFolder> folders = new ArrayList<IDfFolder>();
		boolean createdFolder = false;
		
		if (pathElements.size() == 0) {
			return null;
		}
		
		if (DfLogger.isDebugEnabled(this)) {
			DfLogger.debug(this, "Checking for base path: " + basePath,
						   null, null);
		}
		
		if (requiresBasePathCheck) {
			if (basePath.equals(PATH_SEPARATOR)) {
				if (DfLogger.isTraceEnabled(this)) {
					DfLogger.trace(this, "Base path is repository root",
								  null, null);
				}
			} else if (session.getFolderByPath(basePath) == null) {
				throw new ObjectNotFoundException("Base folder does not exist");
			}
		}
		
		final StringBuilder parentPath = new StringBuilder(basePath);
		if (DfLogger.isTraceEnabled(this)) {
			DfLogger.trace(this,
						   "Calculated initial parent path: " + parentPath,
						   null, null);
		}
				
		for (final PathElement p : pathElements) {
			String folderPath = null;
			if (parentPath.toString().equals(PATH_SEPARATOR)) {
				createdFolder = true;
				folderPath = parentPath.toString() + p.name;
			} else {
				folderPath = parentPath.toString() + PATH_SEPARATOR + p.name;
			}
			
			if (DfLogger.isTraceEnabled(this)) {
				DfLogger.trace(this, "Calculated folder path: " + folderPath,
							   null, null);
			}
			
			boolean requiresFolderCreation = false;
			IDfFolder folder = null;
			
			if (!createdFolder) {
				// already created a folder so this must be a new folder
				//and so we don't need to check if this folder exists
				if (DfLogger.isTraceEnabled(this)) {
					DfLogger.trace(this, "Folder not created yet", null, null);
				}
				requiresFolderCreation = true;
			} else {
				if (requiresTargetFolderCheck) {
					// check if folder exists
					folder = session.getFolderByPath(folderPath);
					if (folder == null) {
						if (DfLogger.isTraceEnabled(this)) {
							DfLogger.trace(this, "Folder does not already exist",
										   null, null);
						}
						requiresFolderCreation = true;
					}
				} else {
					// check for target folder is false so
					// assumption is that it does not exist
					requiresFolderCreation = true;
				}
			}
			
			// create folder if required
			if (requiresFolderCreation) {
				if (parentPath.equals(PATH_SEPARATOR)) {
					if (DfLogger.isDebugEnabled(this)) {
						DfLogger.debug(this, "Creating cabinet: " + p.name,
									   null, null);
					}
					folder = (IDfFolder) session.newObject(
												DfDocbaseConstants.DM_CABINET);
					folder.setObjectName(p.name);
					if (p.acl != null) {
						folder.setACL(p.acl);
					}
					folder.save();
					createdFolder = true;
				} else {
					if (DfLogger.isDebugEnabled(this)) {
						DfLogger.debug(this,
									   "Creating object of {0}: {1}",
									   new String[] { p.type, p.name },
									   null);
					}
					folder = (IDfFolder) session.newObject(p.type);
					folder.setObjectName(p.name);
					if (p.acl != null) {
						folder.setACL(p.acl);
					}
					folder.link(parentPath.toString());
					folder.save();
					createdFolder = true;
				}
			}			
			
			folders.add(folder);
			if (DfLogger.isTraceEnabled(this)) {
				DfLogger.trace(this, "Added new folder to list", null, null);
			}

			if (parentPath.toString().equals(PATH_SEPARATOR)) {
				parentPath.append(p.name);
			} else {
				parentPath.append(PATH_SEPARATOR + p.name);
			}
			if (DfLogger.isTraceEnabled(this)) {
				DfLogger.trace(this,
							   "Updated folder path: " + parentPath.toString(),
							   null, null);
			}
		}

		return folders.toArray(new IDfFolder[] {} );
	}
	
	/**
	 * A path element.
	 * @author Mark Davidson
	 */
	private class PathElement {
		
		/** The object name. */
		public String name = null;
		
		/** The object type name. */
		public String type = null;
		
		/** The ACL. */
		public IDfACL acl = null;
		
		/** Default constructor. */
		public PathElement() {}
	}
}
